package cn.lcvc.xnc.authority.service.impl;

import cn.lcvc.xnc.authority.dao.mapper.SysPermissionMapper;
import cn.lcvc.xnc.authority.dao.mapper.SysRoleMapper;
import cn.lcvc.xnc.authority.dao.mapper.SysRolePermissionMapper;
import cn.lcvc.xnc.authority.model.SysPermission;
import cn.lcvc.xnc.authority.model.query.SysPermisssionQuery;
import cn.lcvc.xnc.authority.service.SysPermissionService;
import cn.lcvc.xnc.authority.utils.MyObjectUtils;
import cn.lcvc.xnc.application.common.exception.model.MyIgnorableException;
import cn.lcvc.xnc.application.common.exception.model.MyServiceException;
import cn.lcvc.xnc.application.common.exception.model.MyWebException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ISysPermissionService extends IBaseService<SysPermission, SysPermisssionQuery> implements SysPermissionService {

    @Autowired
    private SysPermissionMapper mapper;
    @Autowired
    private SysRoleMapper sysRoleMapper;
    @Autowired
    private SysRolePermissionMapper sysRolePermissionMapper;

    /**
     * 设置关联属性
     * @param permission
     */
    private void setParam(SysPermission permission){
        //添加相关关联属性
        //使用角色数
        permission.setRoleNumber(sysRolePermissionMapper.countAllRoleByPermissionId(permission.getId()));
    }

    /**
     * 采用非递归形式，为树形集合设置关联属性
     * 说明：
     * 1.应该是树形结构，即每个点应该都带children
     * 2.如果要设置关联属性的不是树形结构（例如直接查询集合列表），则children不应该带值，否则会严重影响效率
     * @param tree
     */
    private void setTreePermissionsParam(List<SysPermission> tree){
        tree.stream().forEach(permission -> {
            //添加相关关联属性
            setParam(permission);
            List<SysPermission> childrens=permission.getChildren();
            List<SysPermission> childrensTmp=new ArrayList<>();//临时存储对象
            while(childrens.size()>0){
                for(SysPermission permissionChildren:childrens){
                    //添加相关关联属性
                    setParam(permissionChildren);
                    childrensTmp.addAll(permissionChildren.getChildren());
                }
                //遍历结束后
                childrens=new ArrayList<>();//清空集合，不用用clear，否则会把原集合的引用对象移除
                childrens.addAll(childrensTmp);//重置children集合
                childrensTmp=new ArrayList<>();//清空临时集合，不能用clear，需要重新更换地址
            }
        });
    }

    @Override
    public List<SysPermission> getTreeMenus(@Valid @NotEmpty List<SysPermission> permissions) {
        //将权限中的菜单筛选出来
        List<SysPermission> menus=permissions.stream()
                .filter(menu->menu.getType().equals(1)) //只要菜单
                .collect(Collectors.toList());
        //首先筛选出根目录菜单
        List<SysPermission> menusOfRoot=menus.stream().filter(menu->menu.getParentId().equals(Long.valueOf(0))).collect(Collectors.toList());
        //存储权限集合
        menus.removeAll(menusOfRoot);
        menusOfRoot.forEach(menuOfRoot->{
            setChildPermissons(menuOfRoot,menus);
        });
        return menusOfRoot;
    }

    @Override
    public List<SysPermission> findTree(SysPermisssionQuery sysPermisssionQuery) {
        List<SysPermission> tree=new ArrayList();
        if(MyObjectUtils.checkObjAllFieldsIsNull(sysPermisssionQuery)) { // 如果查询条件为空
            tree=mapper.findTree();
        }else{
            //如果有查询条件,因为前端都会传分页之类的数据过来，所以这里要多增加一个查询条件中的默认条件
            tree= mapper.findAll(sysPermisssionQuery);
        }
        //遍历树形结构：使用非递归方式
        setTreePermissionsParam(tree);
        //如果没有查询条件，直接在数据库查询出树形菜单
        return tree;
    }

    /**
     * 20220221优化递归函数，解决了部分情况下出现递归错误的情况
     * @param menuOfParent
     * @param menus
     */
    private void setChildPermissons(SysPermission menuOfParent,List<SysPermission> menus){
        List<SysPermission> menusOfNext=new ArrayList<>();//定义一个记录menuOfParent所包含的子节点的菜单集合，用于遍历，避免iterator重复迭代出错
        Iterator it=menus.iterator();
        while(it.hasNext()){//遍历剩余菜单
            SysPermission menu=(SysPermission)it.next();//获取当前菜单
            if(menu.getParentId().equals(menuOfParent.getId())){//如果该菜单是当前菜单的子菜单
                menuOfParent.getChildren().add(menu);//添加该菜单进集合
                it.remove();//移除该菜单，后面不再进行判断
                //注意，这里不能再继续递归，否则可能在某些情况出现ConcurrentModificationException错误。
                //例如在二级菜单中的第一个菜单有子菜单，那么递归遍历第二个菜单的时候就会出错
                //this.setChildPermissons(menu,menus);
                menusOfNext.add(menu);//用于进入下一次迭代
            }
        }
        //进行递归查询
        menusOfNext.forEach(menuOfNext->{
            this.setChildPermissons(menuOfNext,menus);
        });
    }


    @Override
    public List<SysPermission> getTreePermission(Long userId,boolean isMenu) {
        List<SysPermission> permissionsTree=new ArrayList<>();
        //如果是超级用户
        if(super.isSyperUser(userId.longValue())){
            if(isMenu){//如果要查找菜单类型的权限
                //直接获取树形菜单（不含按钮）
                permissionsTree=mapper.findTreeMenu();
            }else{
                permissionsTree=mapper.findTree();
            }
        }else{
            permissionsTree.addAll(mapper.findTree());//获取数据库所有权限的树形权限
            //获取账户拥有的所有权限，注意查找结果必须是所有权限（含按钮），否则无法追溯到数据库中未保存关系的父级菜单
            Long[] permissionIds=mapper.findIdsByUserIdAndRoleEnabled(userId,true);//获得管理员拥有的菜单id集合
            List<Long> permissionIdList = Arrays.asList(permissionIds);//注意这样转换的集合不是真正的集合，无法使用add和remove方法
            permissionIdList=new CopyOnWriteArrayList<Long>(permissionIdList);//数组直接转换的集合如果用add和remove会出现异常
            try {
                this.setUserTreePermission(permissionsTree,permissionIdList,isMenu);//将用户拥有的菜单在树形菜单上进行标记
            } catch (MyIgnorableException e) {
                //不进行任何处理，仅仅只是为了跳出递归
            }
        }
        return permissionsTree;
    }

    /**
     * 1.方法说明（递归）：根据用户拥有的菜单，对该网站的树形菜单进行处理，最后得到用户拥有的树形菜单完整结构
     * 2.方法设计：
     * （1）如果子菜单是用户拥有的菜单，则其父路径上的所有节点都属于用户的菜单（不是拥有父路径权限，只是把父路径都完整映射给控制层）
     * （2）如果某菜单是用户拥有的菜单，不代表其拥有的子菜单都是用户的菜单（即拥有菜单，不代表拥有子菜单的权限，具体由子菜单和用户关系决定）
     * 3.实现/设计思路：
     *（1）首先递归到最底层的菜单进行判断
     * 遍历某最底层的菜单集合
     * a.如果不是用户拥有的菜单，则将其移除，直到该层的非用户菜单都移除为止
     * b.如果是用户拥有的菜单，则保留，并且将状态改为true，表示该层有菜单是用户的菜单，再反向递归上去时判断父节点是否保留。
     * （2）然后逐层往父菜单走
     * a.如果某节点的底层拥有子菜单，则该节点予以保留；
     * b.如果某节点是用户的直接子菜单，则该节点予以保留
     * c.如果某节点不满足上述a和b的条件，则删除该节点
     * @param permissions 树形菜单，本方法会对该属性进行处理，保证最后该集合是用户拥有的树形菜单完整结构
     * @param permissionIds 用户拥有的菜单id
     * @param isMenu 查找的是否是菜单类型的权限，true表示是；false表示查找所有类型的权限
     * @return permissions集合中是否包含有用户拥有的权限（含按钮）
     */
    private boolean setUserTreePermission(List<SysPermission> permissions,List<Long> permissionIds,boolean isMenu){
        boolean result=false;//表示该菜单是否是用户菜单，在递归中返会给父目录让其知道子菜单中是否有用户菜单（true）
        Iterator it=permissions.iterator();
        while(it.hasNext()){//遍历循环删除必须用迭代器来完成
            SysPermission permission=(SysPermission)it.next();
            List<SysPermission> menusChildren=permission.getChildren();
            if(menusChildren.size()>0){//只要有子节点就一直往下走，直到最底级节点
                if(setUserTreePermission(menusChildren,permissionIds,isMenu)) {//一直递归，直到最低级，然后再逐层返上来，让父菜单知道子菜单是否有用户菜单
                    result = true;//这里返回true，从而实现父节点都知道子菜单有用户菜单
                }else{//如果在该菜单的子菜单中（递归）没有用户菜单
                    if(permissionIds.contains(permission.getId())) {//如果该菜单是直接拥有的菜单
                        permissionIds.remove(permission.getId());//移除该Id对象，下次不在进行判断
                        if(isMenu){ // 如果要查找的是菜单类型
                            if(permission.getType()==2){//如果包含的菜单是按钮类型，则从树形菜单中移除该按钮
                                it.remove();//移除该菜单
                            }
                        }
                        result=true;
                    } else{
                        it.remove();//移除该菜单
                    }
                }
            }else{//如果没有子菜单（即只有到了最底层目录，才进行判断）
                if(permissionIds.contains(permission.getId())) {//如果该菜单是直接拥有的菜单
                    permissionIds.remove(permission.getId());//移除该Id对象，提升性能
                    if(isMenu){ // 如果要查找的是菜单类型
                        if(permission.getType()==2){//如果包含的菜单是按钮类型，则从树形菜单中移除该按钮
                            it.remove();//移除该菜单
                        }
                    }
                    result=true;//不管包含的权限是菜单还是按钮，都返回true，完成父级菜单的追溯
                }else{//如果该菜单不属于用户直接拥有的菜单
                    it.remove();//移除该菜单
                }
            }
        }
        return result;
    }



    @Override
    public void afterFindAll(List<SysPermission> list) {
        for(SysPermission permission:list){
            //添加相关关联属性
            setParam(permission);
        }
    }

    @Override
    protected void beforeGet(Serializable id) {

    }



    /**
     * 根据业务需求重置权限属性，防止前端传错，主要用于添加和编辑
     * @param sysPermission
     */
    private void validateAndResetParam(SysPermission sysPermission){
        if(sysPermission.getParentId()==null){ // 如果为null则说明是根目录菜单
            sysPermission.setParentId(0l);
        }
        if(sysPermission.getType()==1){ //如果是菜单类型
            if(sysPermission.getLinkType()==null){
                throw new MyWebException("操作失败：打开方式不能未空");
            }
            switch (sysPermission.getLinkType()){
                case 1:// 如果打开方式为组件
                    sysPermission.setUrl(null);
                    break;
                case 2://如果打开方式为内链
                    sysPermission.setPath(null);
                    sysPermission.setPermission(null);
                    break;
                case 3://如果打开方式为外链
                    if(StringUtils.hasLength(sysPermission.getUrl())){ //如果地址存在
                        Pattern urlRattern= Pattern.compile("^(http|https)://");
                        if(!urlRattern.matcher(sysPermission.getUrl()).find()){//如果不匹配http或https开头
                            throw new MyWebException("操作失败：外部链接必须以http://或https://开头");
                        }
                    }else{
                        throw new MyWebException("操作失败：外部链接不能为空");
                    }
                    sysPermission.setPath(null);
                    sysPermission.setPermission(null);
            }
        }else{ //如果菜单类型为按钮
            sysPermission.setLinkType(null);
            sysPermission.setComponent(null);
            sysPermission.setRoute(null);
            sysPermission.setPath(null);
            sysPermission.setUrl(null);
        }
    }

    @Override
    protected void beforeSave(SysPermission sysPermission) {
        validateAndResetParam(sysPermission);
        //判断组件名是否重名
        if(sysPermission.getType()==1) { //如果是菜单类型
            if(mapper.countByComponent(sysPermission.getComponent())>0){
                throw new MyServiceException("编辑失败：组件名已存在");
            }
        }
    }

    @Override
    protected void beforeUpdate(SysPermission sysPermission) {
        if(sysPermission.getId()==null){
            throw new MyWebException("编辑失败：标识符不能为空");
        }
        if(sysPermission.getId().equals(sysPermission.getParentId())){
            throw new MyServiceException("编辑失败：不能选择自己作为上级菜单");
        }
        validateAndResetParam(sysPermission);
        //判断组件名是否重名
        if(sysPermission.getType()==1) { //如果是菜单类型
            if(mapper.countByComponentAndIdNot(sysPermission.getComponent(),sysPermission.getId())>0){
                throw new MyServiceException("编辑失败：组件名已存在");
            }
        }
    }

    @Override
    protected void beforeDelete(Serializable[] ids) {
        for(Serializable id:ids){
            if(id!=null){
                //先判断是否有菜单
                SysPermisssionQuery sysPermisssionQuery=new SysPermisssionQuery();
                sysPermisssionQuery.setParentId(Long.valueOf(id.toString()));
                if(mapper.count(sysPermisssionQuery)>0){//如果还有子菜单
                    throw new MyServiceException("删除失败：请先删除子权限信息");
                }
                //判断是否有角色映射到该菜单
                if(sysRolePermissionMapper.countAllRoleByPermissionId(id)>0){
                    throw new MyServiceException("删除失败：请先在角色中移除相关权限");
                }
            }
        }
    }
}
